package com.ddw.meedeeuh;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.swing.JOptionPane;

import com.ddw.meedeeuh.presenters.EditorContainerPresenter;
import com.ddw.meedeeuh.presenters.MainPresenter;
import com.ddw.meedeeuh.presenters.MovieEditorPresenter;
import com.ddw.meedeeuh.presenters.MovieImportPresenter;
import com.ddw.meedeeuh.presenters.MovieScraperPresenter;
import com.ddw.meedeeuh.presenters.MoviesPresenter;
import com.ddw.meedeeuh.presenters.TvShowsPresenter;
import com.ddw.meedeeuh.shared.GlobalSettings;
import com.ddw.meedeeuh.shared.GlobalSettingsFactory;
import com.ddw.meedeeuh.shared.database.DatabaseCallback;
import com.ddw.meedeeuh.shared.events.DBMoviesChangedEvent;
import com.ddw.meedeeuh.shared.events.RefreshDBMoviesEvent;
import com.ddw.meedeeuh.shared.events.RefreshDBMoviesEventHandler;
import com.ddw.meedeeuh.shared.movie.MovieSettings;
import com.ddw.meedeeuh.shared.movie.MovieSettingsFactory;
import com.ddw.meedeeuh.shared.movie.database.DBMovie;
import com.ddw.meedeeuh.shared.movie.database.MovieDB;
import com.ddw.meedeeuh.shared.movie.database.MovieDBFactory;
import com.ddw.meedeeuh.views.EditorContainerViewImpl;
import com.ddw.meedeeuh.views.MainViewImpl;
import com.ddw.meedeeuh.views.MovieEditorViewImpl;
import com.ddw.meedeeuh.views.MovieImportViewImpl;
import com.ddw.meedeeuh.views.MovieScraperViewImpl;
import com.ddw.meedeeuh.views.MoviesViewImpl;
import com.ddw.meedeeuh.views.TvShowsViewImpl;
import com.google.common.eventbus.EventBus;

/**
 * Represents the entry point for meedeeuh
 * @author cmcnicholas
 *
 */
public class Meedeeuh implements MeedeeuhApp {

	private static final Logger LOGGER = Logger.getLogger("com.ddw");
	private final EventBus eventBus = new EventBus();
	private MovieDB movieDatabase = null;
	private GlobalSettings settings;
	private MovieSettings movieSettings;
	private final Map<Integer, DBMovie> dbMovies = new ConcurrentHashMap<Integer, DBMovie>();

	/**
	 * The entry method for meedeuh
	 * @param args optional arguments
	 */
	public static void main(String[] args) {
		new Meedeeuh().setup();
	}

	@Override
	public EventBus getEventBus() {
		return this.eventBus;
	}

	@Override
	public MovieDB getMovieDatabase() {
		if(this.movieDatabase == null) {
			this.movieDatabase = MovieDBFactory.create();
		}

		return this.movieDatabase;
	}

	@Override
	public GlobalSettings getGlobalSettings() {
		return this.settings;
	}

	@Override
	public MovieSettings getMovieSettings() {
		return this.movieSettings;
	}

	@Override
	public synchronized void setDbMovies(Map<Integer, DBMovie> movies) {
		getDbMovies().clear();
		getDbMovies().putAll(movies);
		getEventBus().post(new DBMoviesChangedEvent(getDbMovies()));
	}

	@Override
	public Map<Integer, DBMovie> getDbMovies() {
		return this.dbMovies;
	}

	/**
	 * Sets up meedeeuh
	 */
	private void setup() {
		this.settings = GlobalSettingsFactory.load();
		setupLogging();
		this.movieSettings = MovieSettingsFactory.load();

		final MainPresenter mainPresenter = (MainPresenter) new MainPresenter(this, new MainViewImpl()).setup();
		final MoviesPresenter moviesPresenter = (MoviesPresenter) new MoviesPresenter(this, new MoviesViewImpl()).setup();
		final TvShowsPresenter tvShowsPresenter = (TvShowsPresenter) new TvShowsPresenter(this, new TvShowsViewImpl()).setup();
		final EditorContainerPresenter editorPresenter = (EditorContainerPresenter) new EditorContainerPresenter(this, new EditorContainerViewImpl()).setup();
		new MovieEditorPresenter(this, new MovieEditorViewImpl()).setup();
		new MovieImportPresenter(this, new MovieImportViewImpl(mainPresenter.getView().asComponent())).setup();
		new MovieScraperPresenter(this, new MovieScraperViewImpl(mainPresenter.getView().asComponent())).setup();

		// Setup inner views
		mainPresenter.getView().setMovies(moviesPresenter.getView());
		mainPresenter.getView().setTvShows(tvShowsPresenter.getView());
		mainPresenter.getView().setEditor(editorPresenter.getView());

		// Show the window
		mainPresenter.getView().showView();

		// Listen for refresh movies event
		getEventBus().register(new RefreshDBMoviesEventHandler() {

			@Override
			public void onRefreshDBMovies(RefreshDBMoviesEvent e) {
				getMovieDatabase().movies(new DatabaseCallback<Map<Integer, DBMovie>>() {

					@Override
					public void onSuccess(Map<Integer, DBMovie> result) {
						setDbMovies(result);
					}

					@Override
					public void onFailure(Throwable e) {
						LOGGER.log(Level.SEVERE, "Failed to refresh movies from database", e);
						JOptionPane.showMessageDialog(mainPresenter.getView().asComponent(),
								"Failed to refresh movies from database",
								"Refresh Movies", JOptionPane.ERROR_MESSAGE);
					}

				});
			}

		});
	}

	/**
	 * Sets up the global logging configuration
	 */
	private void setupLogging() {
		LOGGER.setLevel(settings.getLogLevel());
		LOGGER.addHandler(new Handler() {

			private final SimpleFormatter formatter = new SimpleFormatter();

			@Override
			public void publish(LogRecord record) {
				System.out.println(formatter.format(record));
			}

			@Override
			public void flush() {}

			@Override
			public void close() throws SecurityException {}

		});

		// Setup file log
		if(settings.isFileLogEnabled()) {
			final Handler handler;
			try {
				handler = new FileHandler("Log.txt", true);
			} catch (SecurityException e) {
				throw new RuntimeException("Failed to access log file", e);
			} catch (IOException e) {
				throw new RuntimeException("Failed to access log file", e);
			}
			handler.setFormatter(new SimpleFormatter());
			LOGGER.addHandler(handler);
		}
	}

}
